<?php
/**
 * Classe générique permettant l'accès à une base de données
 *
 * Utilise PDO
 */
class Database {
	
	public $driver = "";
	public $host = "localhost";
	public $port = "";
	public $username = "";
	public $password = "";
	public $dbname = "";
	
	public $charset = 'utf8';
	public $pdoDefaultCursor = '';
	
	public $pdo = null;
	
	public static $queries = array();
	
	public function __construct($options = array()) {
		
		$consts_attr = array(
			'DATABASE_DRIVER' => 'driver',
			'DATABASE_HOST' => 'host',
			'DATABASE_PORT' => 'port',
			'DATABASE_LOGIN' => 'username',
			'DATABASE_PASSWORD' => 'password',
			'DATABASE_NAME' => 'dbname',
			'DATABASE_CHARSET' => 'charset',
			'PDO_DEFAULT_CURSOR' => 'pdoDefaultCursor'
		);
		
		foreach ($consts_attr as $c => $attr) {
			if (defined($c)) {
				$this->$attr = constant($c);
			}
		}
		
		$noautoconnect = (isset($options['autoconnect']) && $options['autoconnect'] === false);
		if (!$noautoconnect && !empty($this->driver) && !empty($this->host) && !empty($this->username) && !empty($this->dbname)) {
			$this->connect();
		}
	}
	
	/**
	 * Construit le DSN du driver
	 *
	 * Supporté:
	 * - mysql
	 */
	private function getDSN() {
		$dsn = '';
		$dsn_value_separator = '=';
		$dsn_props_separator = ';';
		
		$options = array();
		$properties = array();
		switch($this->driver) {
			case 'mysql':
				$dsn = "mysql:";
				$properties = array(
					'host' => $this->host,
					'dbname' => $this->dbname,
					'port' => $this->port
				);
				break;
		}
		
		$first = true;
		
		foreach ($properties as $propname => $value) {
			if (!empty($value)) {
				if (!$first) {
					$dsn .= $dsn_props_separator;
				}
				$dsn .= $propname.$dsn_value_separator.$value;
				$first = false;
			}
		}

		return $dsn;
	}
	
	/**
	 * Transforma les options de connexion en options PDO (notamment pour charset)
	 */
	private function getPDOOptions() {
		$options = array();
		switch($this->driver) {
			case 'mysql':
				if (!empty($this->charset)) {
					$options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$this->charset;
				}
				break;
		}

		return $options;
		
	}
	
	/**
	 * Transforme une requête formatté avec @ et $ avec les vrais noms de colonnes et vrais noms de table
	 * Non implémentée
	 */
	private function modelToDB($query) {
		// "(@[a-zA-Z0-9_]+\.){0,1}(\$[a-zA-Z0-9_]+)"
	}
	
	/**
	 * Se connecte à la base de données
	 * Renvoie true si la connexion a été effectuée avec succès, false sinon
	 */
	public function connect() {
		$connected = true;

		try {
			$this->pdo = new PDO($this->getDSN(), $this->username, $this->password, $this->getPDOOptions());
		} catch (PDOException $e) {
			$connected = false;
		}
		return $connected;
	}
	
	/**
	 * Permet d'appeler les fonctions du PDO, sans ré-écrire les méthodes
	 * Pour les fonctions renvoyant un PDOStatement, un EnhancedPDOStatement est renvoyé à la place
	 * Toute les méthodes de PDOStatement sont implémentées, et un attribut lastFetch est ajouté
	 */
	public function __call($methodName, $arguments) {
		$res = null;
		
		// Détection d'une méthode PDO
		if (method_exists($this->pdo, $methodName)) {
			$res = call_user_func_array(array($this->pdo, $methodName), $arguments);
			
			if ($res instanceof PDOStatement) {
				$res = new EnhancedPDOStatement($res, $this->pdo);
			}
			if ($methodName == "prepare" || $methodName == "query") {
				self::$queries[] = $arguments[0];
			}
		}
		return $res;
	}
	
	/**
	 * Fait un prepare et execute directement
	 * De base, toute les chaînes (is_string == true) sont encodées en UTF8, si le charset est UTF8
	 * Sinon, rien n'est fait
	 * Met automatiquement PDO::ATTR_CURSOR à PDO::CURSOR_SCROLL
	 * Renvoie un objet EnhancedPDOStatement (PDOStatement amélioré, avec lastFetch, même méthodes)
	 */
	public function prepareAndExecute($query, $params) {

		$st = $this->prepare($query);

		$newparams = $params;

		foreach ($newparams as $p => &$value) {
			if (is_string($value) && $this->charset == 'utf8' && !self::isUTF8($value)) {
				$value = utf8_encode($value);
			}
		}

		$st->execute($params);
		return $st;
	}
	
	
	/** 
	 * Renvoie true si la chaîne est encodée en UTF8, false sinon
	 *
	 * @link http://www.php.net/manual/fr/function.mb-check-encoding.php#95289
	 */
	public static function isUTF8($str) {

		$len = strlen($str); 
		for($i = 0; $i < $len; $i++){ 
			$c = ord($str[$i]); 
			if ($c > 128) { 
				if (($c > 247)) return false; 
				elseif ($c > 239) $bytes = 4; 
				elseif ($c > 223) $bytes = 3; 
				elseif ($c > 191) $bytes = 2; 
				else return false; 
				if (($i + $bytes) > $len) return false; 
				while ($bytes > 1) { 
					$i++; 
					$b = ord($str[$i]); 
					if ($b < 128 || $b > 191) return false; 
					$bytes--; 
				} 
			} 
		} 
		return true; 
	}
	
	
	
}